/**
 * @name 格式化时间 ts版本
 * @author zs.duan
 * @date 2024-08-21
 * @eamil zs.duan@qq.com
 * @version 2.0.0
 * @description 共有两个方法 formatTime 和 asyncFormatTime 其中 asyncFormatTime 返回一个Promise对象 
 * @description 可以使用 Vue.use(); 挂载到Vue原型上 $formatTime $asyncFormatTime
 * @description chainFormatTime 链式调用
 * */

/**
 * 格式化时间 内部方法
 * @param {Date} date 日期对象
 * @param {String} format 格式化字符串
 * */
const _formatTime = function (date : Date, format : string): string {
    let year = date.getFullYear();
    let month = date.getMonth() + 1;
    let day = date.getDate();
    let hour = date.getHours();
    let minute = date.getMinutes();
    let second = date.getSeconds();
    let millisecond = date.getMilliseconds();

    interface TimePart {
        "M+": number;
        "d+": number;
        "H+": number;
        "m+": number;
        "s+": number;
        "S+": number;
        [key: string]: any;
    }

    let o : TimePart = {
        "M+": month,
        "d+": day,
        "H+": hour,
        "m+": minute,
        "s+": second,
        "S+": millisecond,
    };
    if (/(y+)/.test(format)) {
        format = format.replace(
            RegExp.$1,
            (year + "").substr(4 - RegExp.$1.length)
        );
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(format)) {
            format = format.replace(
                RegExp.$1,
                RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(String(o[k]).length)
            );
        }
    }
    return format;
}


type FormatTimeOptions =  {
    /**格式化 字符串 yyyy-MM-dd HH:mm:ss */ 
    format?: string,
    /**日期对象 */
    date?: Date | string | number,
    /**成功回调 */
    success?: Function,
    /**失败回调 */
    fail?: Function,
    /**完成回调 */
    complete?: Function
} | string | Date | number | undefined;


/**不传参数*/ 
function formatTime(): string
/**只传一个参数*/
function formatTime(options : string | Date | number | undefined) : string
/**完整参数*/ 
function formatTime(options: FormatTimeOptions): string 

/**
 * @name 格式化时间 
 * @description options 建议使用对象传参 也可以使用字符串 | 时间戳 | 日期对象
 * @description 所有参数不传时 默认为当前时间
 * @param {object | string | Date | number} [options] 配置对象 | 日期字符串 | 日期对象 | 时间戳
 * @param {string} options.format 格式化字符串 yyyy-MM-dd HH:mm:ss
 * @param {Date | string | number} [options.date] 日期对象 | 日期字符串 | 时间戳
 * @param {Function} [options.success] 成功返回
 * @param {Function} [options.fail] 失败返回
 * @param {Function} [options.complete] 完成返回
 * @returns {String} 格式化后的时间
 * */
function formatTime(options ?: FormatTimeOptions) : string {
    let option : FormatTimeOptions = {
        date : new Date(),
        format : "yyyy-MM-dd HH:mm:ss",
        success : function (reslut : string) { },
        fail : function (error : object) { },
        complete: function (reslut : object) { }
    }
    if(typeof options == 'object' && options){
        Object.assign(option , options);
    }else if(typeof options == 'string' || typeof options == 'number' || Object.prototype.toString.call(options) === '[object Date]'){
        option.date = options;
    }
    if(typeof option.date == 'string'){
        option.date = option.date.replace(/-/g , '/');
        option.date = option.date.replace(/T/g , ' ');
        option.date = option.date.substr(0 , 19);
    }
    if(option.date){
        try{
            option.date = new Date(option.date);
        }catch{
            option.complete && option.complete({
                code: -1,
                msg: "时间格式错误"
            });
            option.fail && option.fail('时间格式错误');
            throw new Error('时间格式错误');
        }
    }
    option.format = option.format || "yyyy-MM-dd HH:mm:ss";
    let reslut = _formatTime(option.date as Date, option.format);
    option.complete && option.complete({
        code: 200,
        msg: "success",
        data: reslut
    });
    option.success && option.success(reslut);
    return reslut;
}

/**
 * @name 格式化时间 
 * @description options 建议使用对象传参 也可以使用字符串 | 时间戳 | 日期对象
 * @description 所有参数不传时 默认为当前时间
 * @param {object | string | Date | number} [options] 配置对象 | 日期字符串 | 日期对象 | 时间戳
 * @param {String} options.format 格式化字符串 yyyy-MM-dd HH:mm:ss
 * @param {Date | string | number} [options.date] 日期对象 | 日期字符串 | 时间戳
 * @param {Function} [options.success] 成功返回
 * @param {Function} [options.fail] 失败返回
 * @param {Function} [options.complete] 完成返回
 * @returns {Promise} 返回一个Promise对象
 * */
export const asyncFormatTime = function (options = {}) : Promise<string> {
    let data = formatTime(options);
    return new Promise((resolve, reject) => {
        resolve(data);
    })
}

/**
 * 链式调用
 * @param {Date | string | number} [time] 日期对象 | 日期字符串 | 时间戳
 * @returns {Object} 返回一个对象
 * */ 
export function chainFormatTime(time ?: Date | string | number){
    let returnTime = "";
    if(!time)time = new Date();
    if(typeof time == 'string'){
        time = time.replace(/-/g , '/');
        time = time.replace(/T/g , ' ');
        time = time.substr(0 , 19);
    }
    try {
        time = new Date(time);
    }catch (error) {
        throw new Error('时间格式错误');
    }
    function format(this: { format: (formatStr: string) => any; readonly time: string; }, formatStr : string){
        if(typeof formatStr !== 'string')throw new Error('format 必须是字符串');

        returnTime = _formatTime(time as Date, formatStr);
        return this
    }
    
    return {
        format,
        get time(){
            return returnTime;
        }
    }
}


/**返回类型*/
type timeInfo = {
    year : number,
    month : number,
    day : number,
    hour : number,
    minutes : number,
    seconds : number,
    time : number, 
    date : string
}


/**
 * @name 时间处理 只支持链式调用 
 * @description 所有参数不传时 默认为当前时间
 * @description format 格式化时间
 * @description diff 对比时间差
 * @description last 前n天(小时/周)后n天(小时/周)
 * @description week 获取指定日期是星期几 
 * @description day 指定日期是当前(年、月、周)的多少天
 * @description leapYear 今年是否是闰年
 * */
class Time {
    /**直接使用*/ 
    format(): string;
    /**传入时间*/ 
    format(date: string | number | Date): string;
    /**传入参数*/
    format(options: FormatTimeOptions): string;
    /**直接回调*/
    format(callback: (info: timeInfo) => void): string;
    /**传入时间参数和回调*/
    format(date: string | number | Date, callback: (info: timeInfo) => void): string;
    /**传入参数和回调*/
    format(options: FormatTimeOptions, callback: (info: timeInfo) => void): string;
    /**
     * 格式化时间
     * @param {Object | string | number | Date | Function} [options] 配置参数/回调函数 可以是对象 字符串 时间戳 时间对象
     * @param {string | number | Date} [options.date] 时间
     * @param {string} [options.format] 格式化方式 yyyy-MM-dd HH:mm:ss
     * @param {Function} [callback] 回调函数
     * @returns {string} 格式化后的时间
     * */ 
    format(options ?: Function | Date | string | number | FormatTimeOptions, callback ?: Function){
        if(typeof options == 'function'){
            callback = options;
            options = {};
        }
        if(!options) options = {
            date : new Date(),
        };
        if(!callback) callback = (timeInfo : timeInfo) =>{};
        return _format(options as _formatOptions , callback);
    };
    /**
     * 对比时间差
     * @param {Object} options 配置参数
     * @param {Date | string | number} options.startDate 开始时间
     * @param {Date | string | number} [options.endDate] 结束时间 不传默认为当前时间
     * @param {Function} [callback] 回调函数
     * @returns {Object} 对比后的时间差
     * @description 返回的数据格式 {allday : 0 , day : 0 , hours : 0 , allHours : 0 , minutes : 0 , allMinutes : 0 , seconds : 0 , allSeconds : 0 , timeDiff : 0}
     * */ 
    diff(options : _diffOptions , callback ?:Function){
        if(!callback) callback = (timeInfo : _diffTimeInfo) =>{};
        return _diff(options , callback)
    };
    /**
     * 前n天(小时/周)后n天(小时/周) 前用负数 后用正数
     * @param {Object | Function} [options] 配置参数/回调函数
     * @param {Date | string | number} [options.date] 时间 默认为当前时间
     * @param {number} [options.num] 需要前后多少天(小时/周) 默认1
     * @param {string} [options.type] 类型 day hour week 默认 day
     * @param {string} [options.format] 格式化方式 默认 yyyy-MM-dd
     * @param {Function} [callback] 回调函数
     * @returns {Object} 时间信息
     * */ 
    last(options : _lastOptions , callback ?: Function){
        if(!callback) callback = (timeInfo : _lastReturn) =>{};
        if(typeof options == 'function'){
            callback = options;
            options = {
                date : new Date(),
            };
        }
        return _last(options , callback)
    };
    /**
     * 获取当前日期是星期几
     * @param {Date | string | number | Function} [date] 时间/直接为回调函数 默认为当前时间 
     * @param {Function} [callback] 回调函数
     * @return {String} 返回星期几
     * */ 
    week(date ?: Function | Date | string | number , callback ?:Function):string{
        if(typeof date == 'function'){
            callback = date;
            date = undefined;
        }
        if(!callback) callback = (timeInfo : _weekRetrun) =>{};
        return _week(date as Date | string | number | undefined , callback)
    };
    /**
     * 指定日期是当前(年、月、周)的多少天
     * @param {Object | Function} [options] 配置参数/回调函数
     * @param {Date | string | number} [options.date] 时间 默认为当前时间
     * @param {string} [options.type] 类型 year month week 默认 year
     * @param {Function} [callback] 回调函数
     * @returns {Number} 返回指定日期是当前(年、月、周)的多少天
     * */ 
    day(options : Function | _dayOptions , callback ?: Function){
        if(!callback) callback = (timeInfo : _dayReturn) =>{};
        if(typeof options == 'function'){
            callback = options;
            options = {
                date : new Date(),
            };
        }
        return _day(options , callback)
    };
    /**
     * 是否为闰年
     * @param {Date | string | number | Function} [date] 时间/直接为回调函数 默认为当前时间
     * @param {Function} [callback] 回调函数
     * @returns {Boolean} 返回是否为闰年
     * */ 
    leapYear(date ?: Date | string | number , callback ?: (sendData: _leapYearReturn) => void){
        if(!callback) callback = (timeInfo : _leapYearReturn) =>{};
        if(typeof date == 'function'){
            callback = date;
            date = undefined;
        }
        return _leapYear(date , callback);
    };
}

/**获取当前时间的年月日 时分秒 时间戳*/
function _getNowDate(date ?: Date | string | number , format = 'yyyy-MM-dd HH:mm:ss') : timeInfo{
    if(!date)date = new Date();
    if(typeof date == 'string' || typeof date == 'number'){
        date = new Date(date);
    }
    let year = date.getFullYear();
    let month = date.getMonth() + 1;
    let day = date.getDate();
    let hour = date.getHours();
    let minutes = date.getMinutes();
    let seconds = date.getSeconds();
    let time = date.getTime();
    date = formatTime({
        format : format,
        date : date
    })
    return {year,month,day,hour,minutes,seconds ,time , date}
} 

/**格式化时间*/ 
type _formatOptions = {
    /**需要格式件时间*/
    date : Date | string | number,
    /**格式化方式 yyyy-MM-dd HH:mm:ss*/
    format ?: string,
    [key : string] : any
}

type _diffTimeInfo = {
    allday : number,
    day : number,
    hours : number,
    allHours : number,
    minutes : number,
    allMinutes : number,
    seconds : number,
    allSeconds : number,
    timeDiff : number
}
function _format(options : _formatOptions , callback : Function){
    let data = formatTime(options);
    callback({
        dateInfo:_getNowDate(options.date ),
        date : data,
    })
    return data
}

/**对比时间差*/ 
type _diffOptions = {
    /**开始时间*/ 
    startDate : Date | string | number,
    /**结束时间*/
    endDate ?: Date | string | number,
    [key : string] : any
}

function _diff(options : _diffOptions, callback : Function){
    if(typeof options !== 'object')throw new Error('options must be an object');
    if(!options.startDate)throw new Error('startDate must be null');
    if(!options.endDate)options.endDate = new Date();
    if(options.startDate && typeof options.startDate == 'string'){
        options.startDate = options.startDate.replace(/-/g, "/"); 
        options.startDate = options.startDate.replace(/T/g , ' ');
        options.startDate = options.startDate.substr(0 , 19);
    }
    if(options.endDate && typeof options.endDate == 'string'){
        options.endDate = options.endDate.replace(/-/g, "/"); 
        options.endDate = options.endDate.replace(/T/g , ' ');
        options.endDate = options.endDate.substr(0 , 19);
    }
    try {
        options.startDate = new Date(options.startDate);
        options.endDate = new Date(options.endDate);
    }catch (error) {
        throw new Error('时间格式错误');
    }
    let timeDiff = options.startDate.getTime() - options.endDate.getTime();
    let sendData : _diffTimeInfo = {
        allday : 0 , //所有差天数
        day : 0 , //天数
        hours : 0 , //小时
        allHours : 0 , //所有小时
        minutes : 0 , //分钟
        allMinutes : 0 , //所有分钟
        seconds : 0 , //秒
        allSeconds : 0 , //所有秒
        timeDiff : timeDiff,
    }
    sendData.allday = Number((timeDiff / (24 * 3600 * 1000)).toFixed(3));
    sendData.day = Math.floor(timeDiff / (24 * 3600 * 1000));
    sendData.hours = Math.floor(timeDiff % (24 * 3600 * 1000) / (3600 * 1000));
    sendData.allHours = Number(Math.floor(timeDiff / (3600 * 1000)).toFixed(3));
    sendData.minutes = Math.floor(timeDiff % (3600 * 1000) / (60 * 1000));
    sendData.allMinutes = Number(Math.floor(timeDiff / (60 * 1000)).toFixed(3));
    sendData.seconds = Math.floor(timeDiff % (60 * 1000) / 1000);
    sendData.allSeconds = Number(Math.floor(timeDiff / 1000).toFixed(3));
    callback(sendData);
    return sendData;
}

/**前n天(小时/周)后n天(小时/周) 前用负数 后用正数*/
type _lastOptions = {
    date : Date | string | number,
    num ?: number,
    type ?: 'day' | 'hour' | 'week',
    format ?: string,
    [key : string] : any
}

type _lastReturn = {
    previousDate : string , //前n天(小时/周)
    nextDate : string , //后n天(小时/周)
    nowDate : string , //当前时间
    date : string , //当前时间
    dateInfo : timeInfo
}
function _last(options : _lastOptions , callback : Function){
    if(typeof options !== 'object')throw new Error('options must be an object');
    if(!options.date)options.date = new Date();
    if(!options.num)options.num = 1;
    if(!options.type)options.type = 'day';
    if(options.date && typeof options.date == 'string'){
        options.date = options.date.replace(/-/g, "/"); 
        options.date = options.date.replace(/T/g , ' ');
        options.date = options.date.substr(0 , 19);
    }
    try {
        options.date = new Date(options.date);
    }catch (error) {
        throw new Error('时间格式错误');
    }
    let sendData : _lastReturn = {
        previousDate : "" ,
        nextDate : "",
        nowDate : "" ,
        date : "",
        dateInfo : {
            ..._getNowDate(options.date)
        }
    }
    let nextDate = null;
    if(options.type == 'day'){
        nextDate = new Date(options.date.setDate(options.date.getDate() + options.num));
        if(!options.format) options.format = 'yyyy-MM-dd';
        if(options.num < 0){
            sendData.previousDate = _formatTime(nextDate , options.format)
        }else{
            sendData.nextDate = _formatTime(nextDate , options.format)
        }
        sendData.nowDate = _formatTime(options.date , options.format)
    }
    if(options.type == 'hour'){
        nextDate = new Date(options.date.setHours(options.date.getHours() + options.num));
        if(!options.format) options.format = 'yyyy-MM-dd HH:mm';
        if(options.num < 0){
            sendData.previousDate = _formatTime(nextDate , options.format)
        }else{
            sendData.nextDate = _formatTime(nextDate , options.format)
        }
        sendData.nowDate = _formatTime(options.date , options.format)
    }
    if(options.type == 'week'){
        nextDate = new Date(options.date.setDate(options.date.getDate() + options.num * 7));
        if(!options.format) options.format = 'yyyy-MM-dd';
        if(options.num < 0){
            sendData.previousDate = _formatTime(nextDate , options.format)
        }else{
            sendData.nextDate = _formatTime(nextDate , options.format)
        }
        sendData.nowDate = _formatTime(options.date , options.format)
    }
    callback(sendData);
    return sendData;
} 

/**获取日期是星期几*/
type _weekRetrun = {
    format : string , //格式化后的日期
    date : Date , //日期
    dateInfo : timeInfo , //日期信息
    week : string , //星期几
}
function _week(date ?: Date | string | number  , callback ?: Function){
    const weekList = ['日','一','二','三','四','五','六'];
    if(date && typeof date == 'string'){
        date = date.replace(/-/g, "/");
        date = date.replace(/T/g , ' ');
        date = date.substr(0 , 19);
    }
    if(!date)date = new Date();
    try {
        date = new Date(date);
    }catch (error) {
        throw new Error('时间格式错误');
    }
    let week = date.getDay();
    let sendData : _weekRetrun = {
        format : _formatTime(date , 'yyyy-MM-dd') ,
        date : date,
        dateInfo : _getNowDate(date),
        week : weekList[week]
    }
    callback && callback(sendData);
    return weekList[week];
} 

/**
 * 指定日期是当前(年、月、周)的多少天
 * */ 
type _dayOptions = {
    /**需要指定的日期*/ 
    date : Date | string | number , 
    /**类型 year、month、week*/ 
    type ?: 'year' | 'month' | 'week' ,
    [key : string] : any
}

type _dayReturn = {
    format : string , //格式化后的日期
    date : Date , //日期
    dateInfo : timeInfo , //日期信息
    num : number , //当前(年、月、周)的多少天
}

function _day(options : _dayOptions , callback : Function){
    if(typeof options !== 'object')throw new Error('options must be an object');
    if(!options.date)options.date = new Date();
    if(!options.type)options.type = 'year';
    if(options.date && typeof options.date == 'string'){
        options.date = options.date.replace(/-/g, "/"); 
        options.date = options.date.replace(/T/g , ' ');
        options.date = options.date.substr(0 , 19);
    }
    try {
        options.date = new Date(options.date);
    }catch (error) {
        throw new Error('时间格式错误');
    }
    let sendData : _dayReturn = {
        format : _formatTime(options.date , 'yyyy-MM-dd') ,
        date : options.date,
        dateInfo:_getNowDate(options.date),
        num : 0
    }
    let start = null;
    let diff = null;
    let oneDay = 0;
    switch(options.type){
        case 'year':
            start = new Date(options.date.getFullYear(), 0, 0).getTime();
            diff = new Date(options.date).getTime() - start;
            oneDay = 1000 * 60 * 60 * 24;
            sendData.num = Math.floor(diff / oneDay);
            break;
        case 'month':
            start = new Date(options.date.getFullYear(), options.date.getMonth(), 0).getTime();
            diff = new Date(options.date).getTime() - start;
            oneDay = 1000 * 60 * 60 * 24;
            sendData.num = Math.floor(diff / oneDay);
            break;
        case 'week':
            const week = options.date.getDay();
            sendData.num = week;
            break;
        default:
            throw new Error('type is not defined');
    }
    callback(sendData);
    return sendData.num;
}

/**
 * 指定日期是否为闰年 返回
 * */ 

type _leapYearReturn = {
    /**格式化后的日期*/ 
    format : string , 
    /**日期*/ 
    date : Date ,
    /**日期信息*/ 
    dateInfo : timeInfo ,
    /**是否为闰年*/
    leapYear : boolean ,
}
function _leapYear(date?: Date | string | number, callback?: (sendData: _leapYearReturn) => void): boolean {  
    let finalDate: Date;  
    if (!date) {  
        finalDate = new Date();  
    } else if (typeof date === 'string') {  
        // 尝试直接解析字符串为日期  
        finalDate = new Date(date);  
        if (isNaN(finalDate.getTime())) {  
            // 如果解析失败，尝试按年份处理  
            const yearMatch = date.match(/^\d{4}/);  
            if (yearMatch) {  
                finalDate = new Date(yearMatch[0] + '/01/01');  
            } else {  
                throw new Error('时间格式错误');  
            }  
        }  
    } else if (typeof date === 'number') {  
        finalDate = new Date(date);  
    } else {  
        throw new Error('不支持的日期类型');  
    }  
  
    const year = finalDate.getFullYear();  
    const sendData: _leapYearReturn = {  
        format: _formatTime(finalDate, 'yyyy-MM-dd'),  
        date: finalDate,  
        dateInfo: _getNowDate(finalDate),  
        leapYear: false  
    };  
  
    if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {  
        sendData.leapYear = true;  
    }  
  
    callback && callback(sendData);  
    return sendData.leapYear;  
}  

/**
 * @name 时间处理 只支持链式调用 
 * @description 所有参数不传时 默认为当前时间
 * @description format 格式化时间
 * @description diff 对比时间差
 * @description last 前一天后一天
 * @description week 获取指定日期是星期几 
 * @description day 指定日期是当前(年、月、周)的多少天
 * @description leapYear 今年是否是闰年
 * */
export const setTime = new Time();

/**挂载到vue上*/
/**
 * 挂载到Vue原型上
 * */ 
const install = function (Vue : any) {
    Vue.prototype.$formatTime = formatTime;
    Vue.prototype.$asyncFormatTime = asyncFormatTime;
    Vue.prototype.$chainFormatTime = chainFormatTime;
    Vue.prototype.$setTime = setTime;
}

export default {
    formatTime ,
    asyncFormatTime,
    chainFormatTime,
    install,
    setTime
} 

export {formatTime}